// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/app_list/folder_image.h"

#include <string>
#include <utility>

#include "base/macros.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/app_list_item.h"
#include "ui/app_list/app_list_item_list.h"
#include "ui/app_list/app_list_model.h"
#include "ui/gfx/skia_util.h"

namespace app_list {

namespace {

    gfx::ImageSkia CreateSquareBitmapWithColor(int size, SkColor color)
    {
        SkBitmap bitmap;
        bitmap.allocN32Pixels(size, size);
        bitmap.eraseColor(color);
        return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
    }

    bool ImagesAreEqual(const gfx::ImageSkia& image1,
        const gfx::ImageSkia& image2)
    {
        return gfx::BitmapsAreEqual(*image1.bitmap(), *image2.bitmap());
    }

    // Listens for OnFolderImageUpdated and sets a flag upon receiving the signal.
    class TestFolderImageObserver : public FolderImageObserver {
    public:
        TestFolderImageObserver()
            : updated_flag_(false)
        {
        }

        bool updated() const { return updated_flag_; }

        void Reset() { updated_flag_ = false; }

        // FolderImageObserver overrides:
        void OnFolderImageUpdated() override { updated_flag_ = true; }

    private:
        bool updated_flag_;

        DISALLOW_COPY_AND_ASSIGN(TestFolderImageObserver);
    };

} // namespace

class FolderImageTest : public testing::Test {
public:
    FolderImageTest()
        : folder_image_(app_list_model_.top_level_item_list())
    {
    }

    ~FolderImageTest() override { }

    void SetUp() override
    {
        // Populate the AppListModel with three items (to test that the FolderImage
        // correctly supports having fewer than four icons).
        AddAppWithColoredIcon("app1", SK_ColorRED);
        AddAppWithColoredIcon("app2", SK_ColorGREEN);
        AddAppWithColoredIcon("app3", SK_ColorBLUE);

        observer_.Reset();
        folder_image_.AddObserver(&observer_);
    }

    void TearDown() override { folder_image_.RemoveObserver(&observer_); }

protected:
    void AddAppWithColoredIcon(const std::string& id, SkColor icon_color)
    {
        scoped_ptr<AppListItem> item(new AppListItem(id));
        item->SetIcon(CreateSquareBitmapWithColor(kListIconSize, icon_color));
        app_list_model_.AddItem(std::move(item));
    }

    AppListModel app_list_model_;

    FolderImage folder_image_;

    TestFolderImageObserver observer_;

private:
    DISALLOW_COPY_AND_ASSIGN(FolderImageTest);
};

TEST_F(FolderImageTest, UpdateListTest)
{
    gfx::ImageSkia icon1 = folder_image_.icon();

    // Call UpdateIcon and ensure that the observer event fired.
    folder_image_.UpdateIcon();
    EXPECT_TRUE(observer_.updated());
    observer_.Reset();
    // The icon should not have changed.
    EXPECT_TRUE(ImagesAreEqual(icon1, folder_image_.icon()));

    // Swap two items. Ensure that the observer fired and the icon changed.
    app_list_model_.top_level_item_list()->MoveItem(2, 1);
    EXPECT_TRUE(observer_.updated());
    observer_.Reset();
    gfx::ImageSkia icon2 = folder_image_.icon();
    EXPECT_FALSE(ImagesAreEqual(icon1, icon2));

    // Swap back items. Ensure that the observer fired and the icon changed back.
    app_list_model_.top_level_item_list()->MoveItem(2, 1);
    EXPECT_TRUE(observer_.updated());
    observer_.Reset();
    EXPECT_TRUE(ImagesAreEqual(icon1, folder_image_.icon()));

    // Add a new item. Ensure that the observer fired and the icon changed.
    AddAppWithColoredIcon("app4", SK_ColorYELLOW);
    EXPECT_TRUE(observer_.updated());
    observer_.Reset();
    gfx::ImageSkia icon3 = folder_image_.icon();
    EXPECT_FALSE(ImagesAreEqual(icon1, icon3));

    // Add a new item. The observer should not fire, nor should the icon change
    // (because it does not affect the first four icons).
    AddAppWithColoredIcon("app5", SK_ColorCYAN);
    EXPECT_FALSE(observer_.updated());
    observer_.Reset();
    EXPECT_TRUE(ImagesAreEqual(icon3, folder_image_.icon()));

    // Delete an item. Ensure that the observer fired and the icon changed.
    app_list_model_.DeleteItem("app2");
    EXPECT_TRUE(observer_.updated());
    observer_.Reset();
    gfx::ImageSkia icon4 = folder_image_.icon();
    EXPECT_FALSE(ImagesAreEqual(icon3, icon4));
}

TEST_F(FolderImageTest, UpdateItemTest)
{
    gfx::ImageSkia icon1 = folder_image_.icon();

    // Change an item's icon. Ensure that the observer fired and the icon changed.
    app_list_model_.FindItem("app2")
        ->SetIcon(CreateSquareBitmapWithColor(kListIconSize, SK_ColorMAGENTA));
    EXPECT_TRUE(observer_.updated());
    observer_.Reset();
    EXPECT_FALSE(ImagesAreEqual(icon1, folder_image_.icon()));
}

} // namespace app_list
